using System;
using System.Collections.Generic;
using System.Linq;
using MyCRM.Infrastructure.Messaging;
using MyCRM.Infrastructure.Repositories;
using MyCRM.Infrastructure.Storage;

namespace MyCRM.Infrastructure.Eventing
{
    public class EventStore : IEventStore
    {
        private readonly IEventStoreUnitOfWork _unitOfWork;
        private readonly IMessageBus _eventPublisher;

		public class EventDescriptor : IEntity
		{
			public DomainEvent DomainEventData { get; set; }
			public Guid AggregateId { get; set; }

			public Guid Id { get; set; }
			public int Version { get; set; }
			public DateTime TimeStamp { get; set; }
		}

        public EventStore(IMessageBus eventPublisher, IEventStoreUnitOfWork unitOfWork)
        {
            _eventPublisher = eventPublisher;
            _unitOfWork = unitOfWork;
        }

        public void SaveEvents(Guid aggregateId, int version, IEnumerable<DomainEvent> events)
        {

        	var aggregateEvents = this.GetEventsForEntity(aggregateId);
        	
        	if (aggregateEvents != null && aggregateEvents.Count() > 0)
        		if(version < aggregateEvents.Last().Version)
        		{
					throw new ArgumentException(String.Format("version should greater or equal than {0}", version));
        		}

        	events.Do(e => { if (e.AggregateId == Guid.Empty) throw new ArgumentException(); });

            var expectedVersion = version;            
            var eventsToSave = events.Select(x =>
                                       {
                                           expectedVersion++;
                                           x.Version = expectedVersion;
                                           return x;
                                       })
                                       .Select(x => new EventDescriptor()
                                                      {
                                                          DomainEventData = x,
                                                          Version = x.Version,
                                                          AggregateId = aggregateId,
														  Id = Guid.NewGuid(),
                                                          TimeStamp = DateTime.UtcNow
                                                      }).ToList();

            foreach (var eventDescriptor in eventsToSave)
            {
                _unitOfWork.Store(eventDescriptor);
            }

            _unitOfWork.SaveChanges();

            foreach (var eventDescriptor in eventsToSave)
            {
                _eventPublisher.Send(eventDescriptor.DomainEventData);
            }
        }

        public IEnumerable<DomainEvent> GetEventsForEntity(Guid aggregateId)
        {
            return (from eventDescriptor in _unitOfWork.Query<EventDescriptor>()
                    where eventDescriptor.AggregateId == aggregateId
                    orderby eventDescriptor.Version
                    select eventDescriptor).ToList().Select(x => x.DomainEventData);
        }
    }
}